package library.net.retrofit.download;

import android.annotation.SuppressLint;
import android.support.annotation.NonNull;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Objects;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import library.net.retrofit.RxSchedulers;
import library.net.retrofit.callback.IRxCallback;
import library.net.retrofit.callback.RxFileCallback;
import library.net.retrofit.entity.FileEntity;
import library.net.retrofit.response.RxProgressResponseBody;
import okhttp3.ResponseBody;
import retrofit2.Call;

/**
 * @author Created by qiang.hou on 2018/11/23.
 * @version 1.0
 */
public class DownloadUtils {
    private static final int BLOCK_SIZE = 1024;

    /**
     * 适用于后台通过header控制是否支持断点续传的场景
     *
     * @param call             retrofit2
     * @param progressListener IRxCallback
     */
    public static void download(Call<ResponseBody> call, FileEntity entity, final IRxCallback<File> progressListener) {
        call.enqueue(new RxFileCallback(entity) {
            @Override
            public void onProgress(Object tag, long speed, long currentLength, long totalLength) {
                if (null != progressListener) {
                    progressListener.onProgress(tag, speed, currentLength, totalLength);
                }
            }

            @Override
            public void onSuccess(Object tag, File file) {
                if (null != progressListener) {
                    progressListener.onSuccess(tag, file);
                }
            }

            @Override
            public void onError(Object tag, Throwable e) {
                if (null != progressListener) {
                    progressListener.onError(tag, e);
                }
            }
        });
    }

    public static void download(
            Observable<ResponseBody> observable,
            final @NonNull FileEntity fileEntity,
            final IRxCallback progressListener,
            Observer<File> observer) {
        observable
                .map(new Function<ResponseBody, RxProgressResponseBody>() {

                    @Override
                    public RxProgressResponseBody apply(ResponseBody responseBody) {
                        return new RxProgressResponseBody(responseBody, fileEntity, progressListener);
                    }
                })
                .compose(RxSchedulers.<RxProgressResponseBody>io_io())
                .map(new Function<RxProgressResponseBody, File>() {

                    @Override
                    public File apply(RxProgressResponseBody responseBody) throws Exception {
                        return writeFile(responseBody, fileEntity);
                    }
                })
                .compose(RxSchedulers.<File>io_main())
                .subscribe(observer);
    }

    @SuppressLint("CheckResult")
    public static void download(
            Observable<ResponseBody> observable,
            final @NonNull FileEntity fileEntity,
            final IRxCallback progressListener,
            Consumer<File> consumer) {
        observable
                .map(new Function<ResponseBody, RxProgressResponseBody>() {

                    @Override
                    public RxProgressResponseBody apply(ResponseBody responseBody) {
                        return new RxProgressResponseBody(responseBody, fileEntity, progressListener);
                    }
                })
                .compose(RxSchedulers.<RxProgressResponseBody>io_io())
                .map(new Function<RxProgressResponseBody, File>() {

                    @Override
                    public File apply(RxProgressResponseBody responseBody) throws Exception {
                        return writeFile(responseBody, fileEntity);
                    }
                })
                .compose(RxSchedulers.<File>io_main())
                .subscribe(consumer);
    }

    /**
     * 将输入流写入文件
     *
     * @param rxProgressResponseBody data
     * @param fileEntity             local file desc
     * @return File
     */
    private static File writeFile(RxProgressResponseBody rxProgressResponseBody, FileEntity fileEntity) throws Exception {

        if (null == rxProgressResponseBody) {
            return null;
        }

        if (null == fileEntity.destFileDir || fileEntity.destFileDir.equals("")) {
            throw new Exception("文件保存路径不存在！");
        }

        if (!makeFileDir(fileEntity.destFileDir)) {
            throw new Exception("文件保存路径不存在！");
        }

        String saveFilePath = FileEntity.initSavePath(
                fileEntity, Objects.requireNonNull(rxProgressResponseBody.contentType()).toString());
        final File saveFile = new File(saveFilePath);
        if (!saveFile.exists()) {
            if (!saveFile.createNewFile()) {
                throw new Exception("文件保存失败！");
            }
        }

        RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile, "rwd");
        FileChannel channelOut = randomAccessFile.getChannel();
        MappedByteBuffer mappedBuffer = channelOut.map(
                FileChannel.MapMode.READ_WRITE, fileEntity.completedBytes, fileEntity.totalBytes - fileEntity.completedBytes);
        try {
            int everyLength;
            byte[] buffer = new byte[BLOCK_SIZE];
            while ((everyLength = rxProgressResponseBody.byteStream().read(buffer)) != -1) {
                mappedBuffer.put(buffer, 0, everyLength);
            }
        } catch (Exception e) {
            throw new Exception("文件保存失败！");
        } finally {
            fileEntity.commitToSharedPre(fileEntity.url);
            try {
                rxProgressResponseBody.byteStream().close();
                channelOut.close();
                randomAccessFile.close();
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
        return saveFile;
    }

    /**
     * 创建文件本地目录
     *
     * @return boolean
     */
    private static boolean makeFileDir(String fileDir) {
        /* make directory */
        File dir = new File(fileDir);
        /* parent dir is exists? try to create parent dir */
        if (!dir.getParentFile().exists()) {
            if (!dir.getParentFile().mkdirs()) {
                return false;
            }
        }
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                return false;
            }
        }
        return dir.exists();
    }
}
